﻿// Copyright (c) egmkang wang. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using XServer.SDK;

namespace XServer.Config
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Xml;
    using XServer.SDK;

    public class ConfigClass
    {
        public string ClassName;
        public string FileName;
        public string XmlNode;
        public bool IntKey;
        public Type ClassType;
        public List<ConfigElement> Nodes = new List<ConfigElement>();

        public Action<XmlTextReader> ReadFromXml;

        #region Helpers
        public static void Read(string s, ref int v)
        {
            v = !String.IsNullOrWhiteSpace(s) ? Convert.ToInt32(s) : 0;
        }

        public static void Read(string s, ref long v)
        {
            v = !String.IsNullOrWhiteSpace(s) ? Convert.ToInt64(s) : 0;
        }

        public static void Read(string s, ref string v)
        {
            v = string.IsNullOrEmpty(s) ? "" : s;
        }

        public static void Read(string s, ref int[] v)
        {
            if (s == null)
            {
                v = null;
                return;
            }
            var array = new StringView(s).Split('|');
            v = new int[array.Length];

            int index = 0;
            foreach (var str in array)
            {
                v[index++] = str.ToInt32();
            }
        }

        static void ParseTuple(StringView s, ref ValueTuple<int, int> v, char split)
        {
            if (s.Length > 0)
            {
                int index = s.IndexOf(split);
                if (index < 0)
                {
                    throw new ArgumentException(string.Format("'{0}' does not contains '{1}'"
                        , s.ToString(), split));
                }

                StringView t1 = s.Substring(0, index);
                StringView t2 = s.Substring(index + 1);
                v.Item1 = t1.ToInt32();
                v.Item2 = t2.ToInt32();
            }
        }

        static void ParseTuple(StringView s, ref ValueTuple<int, int, int> v, char split)
        {
            if (s.Length > 0)
            {
                int index1 = s.IndexOf(split);
                int index2 = s.IndexOf(split, index1 + 1);
                if (index1 < 0 || index2 < 0)
                {
                    throw new ArgumentException(string.Format("'{0}' does not contains '{1}'"
                        , s.ToString(), split));
                }

                StringView t1 = s.Substring(0, index1);
                StringView t2 = s.Substring(index1 + 1, index2 - index1 - 1);
                StringView t3 = s.Substring(index2 + 1);
                v.Item1 = t1.ToInt32();
                v.Item2 = t2.ToInt32();
                v.Item3 = t3.ToInt32();
            }
        }

        public static void Read(string s, ref ValueTuple<int, int>[] v)
        {
            if (s == null)
            {
                v = null;
                return;
            }
            var array = new StringView(s).Split(',');
            v = new ValueTuple<int, int>[array.Length];

            int index = 0;
            foreach (var str in array)
            {
                var value = new ValueTuple<int, int>();
                ParseTuple(str, ref value, '|');
                v[index++] = value;
            }
        }

        public static void Read(string s, ref ValueTuple<int, int, int>[] v)
        {
            if (s == null)
            {
                v = null;
                return;
            }
            var array = new StringView(s).Split(',');
            v = new ValueTuple<int, int, int>[array.Length];

            int index = 0;
            foreach (var str in array)
            {
                var value = new ValueTuple<int, int, int>();
                ParseTuple(str, ref value, '|');
                v[index++] = value;
            }
        }

        public static void Read(string s, ref ValueTuple<int, int> v)
        {
            v = new ValueTuple<int, int>();
            if (s != null)
            {
                var str = new StringView(s);
                ParseTuple(str, ref v, '|');
            }
        }

        public static void Read(string s, ref ValueTuple<int, int, int> v)
        {
            v = new ValueTuple<int, int, int>();
            if (s != null)
            {
                var str = new StringView(s);
                ParseTuple(str, ref v, '|');
            }
        }
        #endregion
    }

    public class ConfigElement
    {
        public string XmlName;
        public string Name;
        public Type ElementType;
    }

    public static class ConfigMetaData
    {
        static Dictionary<string, ConfigClass> dict = new Dictionary<string, ConfigClass>();
        static Dictionary<string, long> modifyTime = new Dictionary<string, long>();
        static string ConfigPath = ".";

        #region Helpers
        static string GetAttribute(string attrType, string attrName, IEnumerable<CustomAttributeData> attributes)
        {
            foreach (var attr in attributes)
            {
                if (attr.AttributeType.FullName == attrType)
                {
                    return GetAttribute(attrName, attr);
                }
            }
            return null;
        }

        static string GetAttribute(string attrName, CustomAttributeData attr)
        {
            foreach (var named in attr.NamedArguments)
            {
                if (named.MemberName == attrName)
                {
                    return named.TypedValue.Value.ToString();
                }
            }
            foreach (var unnamed in attr.ConstructorArguments)
            {
                return unnamed.Value.ToString();
            }

            return null;
        }

        static ConfigElement GetFieldType(FieldInfo info)
        {
            var attributes = info.CustomAttributes;
            if (attributes == null) return null;
            ConfigElement ret = new ConfigElement();
            foreach (var fieldAttribute in attributes)
            {
                if (typeof(ConfigElementAttribute).FullName == fieldAttribute.AttributeType.FullName)
                {
                    ret.ElementType = info.FieldType;
                    ret.Name = info.Name;
                    ret.XmlName = GetAttribute("Element", fieldAttribute);
                    return ret;
                }
            }

            return null;
        }
        #endregion

        public static void InitConfigPath(string path)
        {
            ConfigPath = path;
        }

        public static void InitConfigMetaData()
        {
            var intType = typeof(IConfig<int>);
            var stringType = typeof(IConfig<string>);

            var assemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach (var asm in assemblies)
            {
                if (asm.GlobalAssemblyCache || asm.IsDynamic)
                {
                    continue;
                }
                var types = asm.ExportedTypes;
                foreach (var typeInfo in types)
                {
                    if (!(typeInfo.IsSubclassOf(intType) ||
                        typeInfo.IsSubclassOf(stringType)) ||
                        typeInfo.IsAbstract)
                    {
                        continue;
                    }

                    try
                    {
                        ConfigClass configInfo = new ConfigClass();
                        configInfo.ClassName = typeInfo.Name;
                        configInfo.ClassType = typeInfo;
                        configInfo.IntKey = typeInfo.IsSubclassOf(intType);
                        configInfo.FileName = GetAttribute(typeof(ConfigRootAttribute).FullName, "File", typeInfo.CustomAttributes);
                        configInfo.XmlNode = GetAttribute(typeof(ConfigRootAttribute).FullName, "XmlNode", typeInfo.CustomAttributes);

                        foreach (var field in typeInfo.GetFields().Reverse())
                        {
                            var element = GetFieldType(field);
                            if (element != null)
                            {
                                configInfo.Nodes.Add(element);
                            }
                        }
                        CodeGenerator.ILGen(configInfo);

                        dict.Add(string.Format("{0}@{1}", configInfo.FileName, configInfo.XmlNode), configInfo);
                        if (!modifyTime.ContainsKey(configInfo.FileName))
                            modifyTime.Add(configInfo.FileName, 0);
                    }
                    catch (Exception e)
                    {
                        LoggerProvider.Logger.Error(e.Message);
                    }
                }
            }

            CodeGenerator.Save();
        }

        public static ConfigClass GetConfig(string fileName, string path)
        {
            if (dict.Count <= 0)
            {
                InitConfigMetaData();
            }
            ConfigClass c = null;
            dict.TryGetValue(String.Format("{0}@{1}", fileName, path), out c);
            return c;
        }

        static long GetFileModifyTime(string fileName)
        {
            return Platform.GetFileModifyTime(fileName);
        }

        static bool LoadSingleFile(string fileName)
        {
            var modify = GetFileModifyTime(fileName);
            if (modifyTime[fileName] != 0 &&
                modifyTime[fileName] == modify)
            {
                return false;
            }

            using (var reader = new XmlTextReader(string.Format("{0}/{1}", ConfigPath, fileName)))
            {
                reader.WhitespaceHandling = WhitespaceHandling.None;
                ConfigClass config = null;
                try
                {
                    while (reader.Read())
                    {
                        if (reader.NodeType != XmlNodeType.Element)
                        {
                            continue;
                        }
                        config = GetConfig(fileName, reader.Name);
                        if (config != null && config.ReadFromXml == null)
                        {
                            config.ReadFromXml = (Action<XmlTextReader>)CodeGenerator.GetMethod(config.ClassName).CreateDelegate(typeof(Action<XmlTextReader>));
                        }
                        if (config != null && config.ReadFromXml != null)
                        {
                            LoggerProvider.Logger.Info("Begin Parse {0}:{1}", fileName, config.ClassName);
                            config.ReadFromXml(reader);
                        }
                    }
                }
                catch (Exception e)
                {
                    LoggerProvider.Logger.Error("FileName:{0}, Config:{1}, ID:{3}, {2}"
                        , fileName, config.ClassName, e.Message, reader.GetAttribute(0));
                }
            }
            modifyTime[fileName] = 1;

            return true;
        }

        public static void LoadAllConfig()
        {
            if (modifyTime.Count <= 0)
            {
                InitConfigMetaData();
            }

            var list = new string[modifyTime.Count];
            modifyTime.Keys.CopyTo(list, 0);

            foreach (var fileName in list)
            {
                LoadSingleFile(fileName);
            }

            var set = Singleton<HashSet<ISwapable>>.Instance;
            foreach (var item in set)
            {
                item.Check();
            }
            foreach (var item in set)
            {
                item.Swap();
            }
        }
    }
}
